#include "String.h"
#include "Error.h"
#include "OrdColl.h"
#include "System.h"
#include "MemBuf.h"

#include "SunIAC.h"

class IACClient;
class WindowSystem;

WindowSystem *gWindowSystem;

static int dupid= 1;

//---- IACClient ---------------------------------------------------------------

class IACClient : public SunIAC {
public:
	IACClient(int fd);
	~IACClient();
	void Broadcast2(SunMsg *m);
	void FindFirst(SunMsg *m);
	void AnalyseBroadCast(SunMsg *m);
	void ServerRequest(SunMsg *m);
	void Dispatch(SunMsg *m);
	void SendUpError(int);

	static IACClient *FindByName(const char *name);
	static IACClient *FindById(int id);

private:
	static int cnt;
	static OrdCollection *clients;
};

//---- PendingRequest ----------------------------------------------------------

class PendingRequest : public Object {
public:
	PendingRequest(const char *nm, IACClient *to, int ser);
	static void Check(const char *nm, int sid);
protected:
	~PendingRequest();
	void Process(const char *nm, int sid);
private:
	char *name;
	int serial;
	IACClient *replyto;

	static OrdCollection *pending;
};

OrdCollection *PendingRequest::pending;

PendingRequest::PendingRequest(const char *nm, IACClient *to, int ser)
{
	name= strsave(nm);
	replyto= to;
	serial= ser;
	if (pending == 0)
		pending= new OrdCollection;
	pending->Add(this);
}

PendingRequest::~PendingRequest()
{
	SafeDelete(name);
}

void PendingRequest::Process(const char *nm, int sid)
{
	if (strcmp(name, nm) == 0) {
		replyto->SendReply(1, serial, 0, form("%d", sid));
		pending->RemovePtr(this);
		delete this;
	}
}

void PendingRequest::Check(const char *nm, int sid)
{
	if (pending) {
		pending->ForEach(PendingRequest,Process)(nm, sid);
		if (pending->Size() <= 0) {
			SafeDelete(pending);
		}
	}
}

//---- IACClient ---------------------------------------------------------------

int IACClient::cnt= 1000;
OrdCollection *IACClient::clients;

IACClient::IACClient(int fd) : SunIAC(fd)
{
	if (clients == 0)
		clients= new OrdCollection;
	clients->Add(this);
	sid= cnt++;
}

IACClient::~IACClient()
{
	if (name)
		Broadcast2(new SunMsg(0, sid, 0, form("remove %s", name)));
	if (clients) {
		clients->RemovePtr(this);
		if (clients->Size() <= 0) {
			SafeDelete(clients);
		}
	}
}

void IACClient::SendUpError(int code)
{
	SunIAC::SendUpError(code);
	// Remove();
}

IACClient *IACClient::FindByName(const char *name)
{
	Iter next(clients);
	register IACClient *cl;

	while (cl= (IACClient*) next())
		if (cl->name && strcmp(cl->name, name) == 0)
			return cl;
	return 0;
}

IACClient *IACClient::FindById(int id)
{
	Iter next(clients);
	register IACClient *cl;

	while (cl= (IACClient*) next())
		if (cl->sid == id)
			return cl;
	return 0;
}

void IACClient::Broadcast2(SunMsg *m)
{
	register IACClient *cl;
	Iter next(clients);

	while (cl= (IACClient*) next())
		if (cl != this)
			cl->SendMessage(new SunMsg(sid, m->From(), 0, m->Data(), m->Len()));
}

void IACClient::FindFirst(SunMsg *m)
{
	register IACClient *cl;
	Iter next(clients);
	int rc= 0;

	while (cl= (IACClient*) next()) {
		if (cl != this) {
			cl->SendMessage(new SunMsg(sid, m->From(), m->Serial(), m->Data(),
																	m->Len()));
			SunMsg *reply= 0;
			//SunMsg *reply= cl->WaitForReply(m->Serial(), 5000);
			if (reply && strncmp(reply->Data(), "0", 1) != 0) {
				rc= cl->sid;
				break;
			}
		}
	}
	char *ret= form("%d", rc);
	SendReply(m->From(), m->Serial(), rc, ret, strlen(ret));
}

void IACClient::AnalyseBroadCast(SunMsg *m)
{
	char req[1000], buf[1000];

	req[0]= buf[0]= 0;
	if (sscanf(m->Data(), "%s %s", req, buf) > 0) {
		if (strcmp(req, "enroll") == 0) {
			char nm[200];
			strcpy(nm, BaseName(buf));
			if (FindByName(nm)) {
				fprintf(stderr, "Duplicate Tool %s; ", nm);
				sprintf(nm, "%s-%d", BaseName(buf), dupid++);
				fprintf(stderr, "changing name to %s\n", nm);
			}
			SetName(nm);
			PendingRequest::Check(BaseName(buf), sid);
		} else if (strcmp(req, "remove") == 0) {
			clients->RemovePtr(this);
			// Remove();
		}
	}
}

void IACClient::ServerRequest(SunMsg *m)
{
	char req[1000], buf[1000];
	int fr= m->From();

	req[0]= buf[0]= 0;
	if (sscanf(m->Data(), "%s %s", req, buf) > 0) {
		if (strcmp(req, "findname") == 0) {
			IACClient *cll= FindByName(buf);
			if (cll)
				SendReply(fr, m->Serial(), 0, form("%d", cll->sid));
			else
				SendReply(fr, m->Serial(), -1, form("%s not found", buf));
		} else if (strcmp(req, "debug") == 0) {
			gDebug= TRUE;
		} else if (strcmp(req, "quit") == 0) {
			SendReply(fr, m->Serial(), 0, "server killed");
			gSystem->ExitControl();
		} else if (strcmp(req, "start") == 0) {
			IACClient *cll= FindByName(buf);
			if (cll)
				SendReply(fr, m->Serial(), 0, form("%d", cll->sid));
			else {
				new PendingRequest(buf, this, m->Serial());
				char *t= form("%s/tools/%s &", gEtDir, buf);
				fprintf(stderr, "Starting Tool: %s\n", t);
				gSystem->Exec(t);
			}
		} else
			SendReply(fr, m->Serial(), -1, "unknown server request");
	}
}

void IACClient::Dispatch(SunMsg *m)
{
	IACClient *cll;

	switch (m->To()) {

	case 0:     // broadcast
		Broadcast2(m);
		AnalyseBroadCast(m);
		break;

	case 1:     // server request
		ServerRequest(m);
		break;
		
	case 2:     // poll request
		FindFirst(m);
		break;

	default:    // client request
		if (cll= FindById(m->To()))
			cll->SendMessage(m);
		else
			SendReply(1, m->Serial(), -1, "no such client");
		break;
	}
}

//---- AcceptHandler -----------------------------------------------------------

class AcceptHandler : public SysEvtHandler {
public:
	AcceptHandler(int r) : SysEvtHandler(r)
		{ gSystem->AddFileInputHandler(this); }
	bool Notify();
};

bool AcceptHandler::Notify()
{
	int msgsock= gSystem->AcceptConnection(GetResourceId());
	if (msgsock < 0)
		SysError("Notify", "accept");
	else
		new IACClient(msgsock);
	return TRUE;
}

//---- Main --------------------------------------------------------------------

main(int argc, char *argv[])
{
	int sock;

	ETInit(argc, argv);

	gIACDebug= TRUE;

	if ((sock= gSystem->AnnounceTcpService(SERVICENAME)) >= 0)
		new AcceptHandler(sock);
		
	if ((sock= gSystem->AnnounceUnixService(SERVERNAME)) >= 0)
		new AcceptHandler(sock);

	if (sock < 0) {
		fprintf(stderr, "no socket found\n");
		return 1;
	}

	gSystem->Control();

	return 0;
}

